home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1992 June: ROMin Holiday / ADC Developer CD (1992-06) (''ROMin Holiday'')_iso / Developer Connection - 06-1992.iso / Developer Essentials / DTS Sample Code / System 7.0 Samples / MacShell / TextEditControl.c < prev    next >
Encoding:
Text File  |  1991-12-04  |  59.1 KB  |  2,314 lines  |  [TEXT/MPS ]

  1. /*
  2. ** Apple Macintosh Developer Technical Support
  3. **
  4. ** Program:         texteditcontrol.c
  5. ** Written by:      Eric Soldan
  6. ** Based on:        TESample, by Bryan Stearns
  7. ** Suggestions by:  Forrest Tanaka, Dave Radcliffe
  8. **
  9. ** Copyright © 1990-1991 Apple Computer, Inc.
  10. ** All rights reserved.
  11. */
  12.  
  13. /* This is a control implementation of TextEdit.  The advantages to this are:
  14. **
  15. ** 1) Makes using TextEdit in a non-dialog window easy.
  16. ** 2) The TextEdit record is automatically associated with the window, since
  17. **    it is in the window's control list.
  18. ** 3) The TextEdit control can have scrollbars associated with it, and these
  19. **    are also kept in the window's control list.
  20. ** 4) Updating of the TextEdit record is much simpler, since all that is
  21. **    necessary is to draw the control (or all the window's controls with
  22. **    a DrawControls call).
  23. ** 5) There are simple calls to handle TextEdit events.
  24. ** 6) Undo is already supported.
  25. ** 7) A document length can be specified.  This length will not be exceeded
  26. **    when editing the TextEdit record.
  27. ** 8) When you close the window, the TextEdit record is disposed of.
  28. **    (This automatic disposal can easily be defeated.)
  29. **
  30. **
  31. ** To create a TextEdit control, you only need a single call.  For example:
  32. **
  33. **    CTENew(rViewCtl,            Resource ID of view control for TextEdit control.
  34. **           window,                Window to hold TERecord.
  35. **           &teHndl,                Return handle for TERecord.
  36. **           &ctlRect,            Rect for TextEdit view control.
  37. **           &destRect,            destRect for TERecord
  38. **           &viewRect,            viewRect for TERecord
  39. **           &borderRect,            Used to frame a border.
  40. **           32000,                Max size for TERecord text.
  41. **           cteVScrollLessGrow    TERecord read-write, with vertical scroll
  42. **                                that leaves space for grow box.
  43. **    );
  44. **
  45. ** If you create a TextEdit control that is read-only, you will not be able
  46. ** to edit it, of course.  There will also be no blinking caret for that
  47. ** TextEdit control.  You will be able to select text and copy to the
  48. ** clipboard, but that is all.
  49.  
  50. ** Simply create destRect, viewRect, and borderRect appropriately, and
  51. ** then call CTENew (which stands for Control TENew).  If teHndl is returned
  52. ** nil, then CTENew failed.  Otherwise, you now have a TextEdit control in
  53. ** the window.
  54. **
  55. ** NOTE: There is a TextEdit bug (no way!!) such that you may need to set the
  56. **       viewRect right edge 2 bigger than the right edge of destRect.  If you
  57. **       do not do this, then there will be some clipping on the right edge in
  58. **       some cases.  Of course, you may want this.  You may want horizontal
  59. **       scrolling, and therefore you would want the destRect substantially
  60. **       larger than the viewRect.  If you don't want horizontal scrolling,
  61. **       then you probably don't want any clipping horizontally, and therefore
  62. **       you will need to set destRect.right 2 less than viewRect.right.
  63. **
  64. **
  65. ** If the CTENew call succeeds, you then have a TextEdit control in your
  66. ** window.  It will be automatically disposed of when you close the window.
  67. ** If you don't waht this to happen, then you can detatch it from the
  68. ** view control which owns it.  To do this, you would to the following:
  69. **
  70. **  viewCtl = CTEViewFromTE(theTextEditHndl);
  71. **  if (viewCtl) SetCRefCon(viewCtl, nil);
  72. **
  73. ** The view control keeps a reference to the TextEdit record in the refCon.
  74. ** If the refCon is cleared, then the view control does nothing.  So, all that
  75. ** is needed to detatch a TextEdit record from a view control is to set the
  76. ** view control's refCon nil.  Now if you close the window, you will still
  77. ** have the TextEdit record.
  78. **
  79. **
  80. ** To remove a TextEdit control completely from a window, you make one call:
  81. **
  82. **  CTEDispose(theTextEditHndl);
  83. **
  84. ** This disposes of the TextEdit record, the view control, and any scrollbar
  85. ** controls that were created when the TextEdit control was created with
  86. ** the call CTENew.
  87. **
  88. **
  89. ** Events for TextEdit record are handled nearly automatically.  You can
  90. ** make one of 3 calls:
  91. **
  92. **  CTEClick(eventPtr);
  93. **  CTEEvent(eventPtr);
  94. **  CTEKey(eventPtr);
  95. **
  96. ** In each case, if the event was handled, true is returned.  CTEEvent simply
  97. ** calls either CTEClick or CTEKey, whichever is appropriate.
  98. **
  99. **
  100. ** Another call you will want to use is CTEEditMenu.  This is used to set the
  101. ** state of cut/copy/paste/clear for TextEdit controls.  It checks the active
  102. ** control to see if text is selected, if the control is read-only, etc.
  103. ** Based on this information, it sets cut/copy/paste/clear either active
  104. ** or inactive.  If any menu items are set active, it returns true.
  105. **
  106. **
  107. ** One more high-level call is CTEUndo().  In response to an undo menu item
  108. ** being selected by the user, just call CTEUndo(), and the edits the user
  109. ** has made will be undone.  (This includes undoing an undo.)
  110. **
  111. **
  112. ** The last high-level call is CTEClipboard.  Call it when you want to do a
  113. ** cut/copy/paste/clear for the active TextEdit control.  The value to pass
  114. ** is as follows:
  115. **
  116. **  2: cut
  117. **  3: copy
  118. **  4: paste
  119. **  5: clear
  120. **
  121. ** These are the same values you would pass to a DA for these actions.
  122. */
  123.  
  124.  
  125.  
  126. /*****************************************************************************/
  127.  
  128.  
  129.  
  130. #ifndef __TEXTEDITCONTROL__
  131. #include "TextEditControl.h"
  132. #endif
  133.  
  134. #ifndef __CONTROLS__
  135. #include <Controls.h>
  136. #endif
  137.  
  138. #ifndef __ERRORS__
  139. #include <Errors.h>
  140. #endif
  141.  
  142. #ifndef __EVENTS__
  143. #include <Events.h>
  144. #endif
  145.  
  146. #ifndef __MEMORY__
  147. #include <Memory.h>
  148. #endif
  149.  
  150. #ifndef __MENUS__
  151. #include <Menus.h>
  152. #endif
  153.  
  154. #ifndef __OSUTILS__
  155. #include <OSUtils.h>
  156. #endif
  157.  
  158. #ifndef __RESOURCES__
  159. #include <Resources.h>
  160. #endif
  161.  
  162. #ifndef __SCRAP__
  163. #include <Scrap.h>
  164. #endif
  165.  
  166.  
  167.  
  168. /*****************************************************************************/
  169.  
  170.  
  171.  
  172. #define kLeftArrow    28
  173. #define kRightArrow    29
  174. #define kUpArrow    30
  175. #define kDownArrow    31
  176.  
  177.  
  178. typedef struct cdefRsrcJMP {
  179.     long    moveInst;
  180.     long    jsrInst;
  181.     short    jmpInst;
  182.     long    jmpAddress;
  183. } cdefRsrcJMP;
  184. typedef cdefRsrcJMP *cdefRsrcJMPPtr, **cdefRsrcJMPHndl;
  185.  
  186.  
  187.  
  188. /*****************************************************************************/
  189.  
  190.  
  191.  
  192. static pascal long    CTECtl(short varCode, ControlHandle ctl, short msg, long parm);
  193. static short        theViewID;
  194. static Boolean        canGoSlow;
  195. static Handle        scrollProc = nil;
  196.  
  197.  
  198.  
  199. /*****************************************************************************/
  200.  
  201.  
  202.  
  203. static TEHandle            gActiveTEHndl;
  204.     /* Currently active TextEdit record.  (nil if none active.) */
  205.  
  206. static TEHandle            gFoundTEHndl;
  207.     /* Global value used to return info from the TextEdit control proc. */
  208.  
  209. static ControlHandle    gFoundViewCtl;
  210.     /* Global value used to return info from the TextEdit control proc. */
  211.  
  212. static pascal void        VActionProc(ControlHandle scrollCtl, short part);
  213. static pascal void        HActionProc(ControlHandle control, short part);
  214. static void                AdjustOneScrollValue(TEHandle teHndl, ControlHandle ctl, Boolean vert);
  215.  
  216. ClikLoopProcPtr        gDefaultClikLoop;
  217.     /* The clikLoop TextEdit wants to use.  Our custom clikLoop must call
  218.     ** this as well.  The default TextEdit clikLoop is stored here.
  219.     */
  220.  
  221. #define kTELastForWind    1
  222. #define kCrChar            13
  223.  
  224.  
  225. /*****************************************************************************/
  226. /*****************************************************************************/
  227.  
  228.  
  229.  
  230. /* Activate this TextEdit record.  If another is currently active, deactivate
  231. ** that one.  The view control for this TextEdit record is also flagged to
  232. ** indicate which was the last active one for this window.  If the previous
  233. ** active TextEdit record was in the same window, then flag the old one off
  234. ** for this window.  The whole point for this per-window flagging is so that
  235. ** activate events can reactivate the correct TextEdit control per window.
  236. */
  237.  
  238. #pragma segment Controls
  239. void    CTEActivate(Boolean active, TEHandle teHndl)
  240. {
  241.     WindowPtr        tePort, oldPort;
  242.     ControlHandle    viewCtl;
  243.     TEHandle        te;
  244.     CTEDataHndl        teData;
  245.  
  246.     if (!teHndl) return;
  247.  
  248.     if (!active) {
  249.         GetPort(&oldPort);
  250.         SetPort(tePort = (*teHndl)->inPort);
  251.         TEDeactivate(teHndl);
  252.         if (teHndl == gActiveTEHndl) gActiveTEHndl = nil;
  253.         viewCtl = CTEViewFromTE(teHndl);
  254.         CTEUpdate(teHndl, viewCtl, true);
  255.         SetPort(oldPort);
  256.         return;
  257.     }
  258.  
  259.     if (!(viewCtl = CTEViewFromTE(teHndl))) return;
  260.  
  261.     teData = (CTEDataHndl)(*viewCtl)->contrlData;
  262.     (*teData)->mode |= cteActive;
  263.     GetPort(&oldPort);
  264.     SetPort(tePort = (*teHndl)->inPort);
  265.     TEActivate(gActiveTEHndl = teHndl);
  266.     CTEUpdate(teHndl, viewCtl, true);
  267.     SetPort(oldPort);
  268.         /* Let TextEdit know that it is supposed to be active. */
  269.  
  270.     for (viewCtl = nil;;) {
  271.         viewCtl = CTENext(tePort, &te, viewCtl);
  272.         if (!viewCtl) break;
  273.         if (te != teHndl) {
  274.             teData = (CTEDataHndl)(*viewCtl)->contrlData;
  275.             (*teData)->mode &= (0xFFFF - cteActive);
  276.             CTEActivate(false, te);
  277.         }
  278.     }
  279. }
  280.  
  281.  
  282.  
  283. /*****************************************************************************/
  284.  
  285.  
  286.  
  287. /* Select or reselect a range of text without flashing or lurching. */
  288.  
  289. #pragma segment Controls
  290. void    CTEFakeClick(short newStart, short newEnd, Boolean extend, TEHandle teHndl)
  291. {
  292.     WindowPtr    oldPort;
  293.     short        lineHeight, oldStart, oldEnd, newLoc, dx, dy, selTop, selBot;
  294.     Point        fakeClick;
  295.     Rect        viewRct;
  296. #ifdef THINK_PRE_5
  297.     long        tempLong;
  298. #endif
  299.  
  300.     if (!teHndl) return;
  301.  
  302.     if (newStart < 0) newStart = 0;
  303.     if (newEnd > (*teHndl)->teLength) newEnd = (*teHndl)->teLength;
  304.  
  305.     oldStart = (*teHndl)->selStart;
  306.     oldEnd   = (*teHndl)->selEnd;
  307.     if ((extend) && (oldStart == newStart) && (oldEnd == newEnd)) return;
  308.  
  309.     GetPort(&oldPort);
  310.     SetPort((*teHndl)->inPort);
  311.     TEAutoView(false, teHndl);
  312.     lineHeight = (*teHndl)->lineHeight;
  313.  
  314.     newLoc = (newStart < oldStart) ? newStart : newEnd;
  315.  
  316. #ifndef THINK_PRE_5
  317.         fakeClick = TEGetPoint(newLoc, teHndl);
  318. #else
  319.         tempLong = TEGetPoint(newLoc, teHndl);
  320.         BlockMove(&tempLong, &fakeClick, 4L);
  321. #endif
  322.     fakeClick.v -= (lineHeight >> 1);
  323.     if (newLoc != TEGetOffset(fakeClick, teHndl)) fakeClick.v += lineHeight;
  324.  
  325.     (*teHndl)->clickTime = 0L;
  326.         /* Keystrokes for extend-select shouldn't be able to dbl-click. */
  327.  
  328.     canGoSlow = false;
  329.         /* Since we are using TEClick to handle the hilight, make sure that
  330.         ** our slow-select feature doesn't work for keys. */
  331.  
  332.     TEClick(fakeClick, extend, teHndl);
  333.         /* Do new selection without flash like extend-select with mouse does it. */
  334.  
  335.     (*teHndl)->clickTime = 0L;
  336.         /* We just did a key, not a click, so reset the click timer. */
  337.  
  338.     selBot = (fakeClick.v += (lineHeight >> 1));
  339.     selTop = (fakeClick.v -= lineHeight);
  340.  
  341.     viewRct = (*teHndl)->viewRect;
  342.     dy = 0;
  343.     if (selTop < viewRct.top)    dy = selTop - viewRct.top;
  344.     if (selBot > viewRct.bottom) dy = selBot - viewRct.bottom;
  345.  
  346.     --fakeClick.h;
  347.     dx = 0;
  348.     if (fakeClick.h < viewRct.left)  dx = fakeClick.h - viewRct.left;
  349.     if (fakeClick.h > viewRct.right) dx = fakeClick.h - viewRct.right + 1;
  350.  
  351.     if (dx | dy) TEScroll(-dx, -dy, teHndl);
  352.  
  353.     TEAutoView(true, teHndl);
  354.     (*teHndl)->clikLoop = ASMTECLIKLOOP;
  355.     AdjustTEBottom(teHndl);
  356.     AdjustScrollValues(teHndl);
  357.     SetPort(oldPort);
  358. }
  359.  
  360.  
  361.  
  362. /*****************************************************************************/
  363.  
  364.  
  365.  
  366. /* This is called when a mouseDown occurs in the content of a window.  It
  367. ** returns true if the mouseDown caused a TextEdit action to occur.  Events
  368. ** that are handled include if the user clicks on a scrollbar that is
  369. ** associated with a TextEdit control.
  370. */
  371.  
  372. #pragma segment Controls
  373. Boolean    CTEClick(EventRecord *event)
  374. {
  375.     WindowPtr        oldPort, window;
  376.     Point            mouseLoc;
  377.     TEHandle        te, teActive;
  378.     ControlHandle    ctlHit, viewCtl;
  379.     CTEDataHndl        teData;
  380.     Boolean            vert;
  381.     short            extendSelect, part, value;
  382.  
  383.     GetPort(&oldPort);
  384.     if (!(window = FrontWindow())) return(false);
  385.  
  386.     SetPort(window);
  387.     mouseLoc = event->where;
  388.     GlobalToLocal(&mouseLoc);
  389.  
  390.     if (CTEFindCtl(window, event, &te, &ctlHit)) {
  391.             /* See if the user clicked directly on the view control for a
  392.             ** TextEdit record.  If so, we definitely have some work to do.
  393.             */
  394.         if (te != gActiveTEHndl) {
  395.             CTEActivate(true, te);
  396.             SetPort(oldPort);
  397.             return(true);
  398.                 /* If user clicked on TextEdit control other than the
  399.                 ** currently active control, then activate it.  This is our
  400.                 ** only action in this case.
  401.                 */
  402.         }
  403.         extendSelect = ((event->modifiers & shiftKey) != 0);
  404.             /* Extend-select may be occuring. */
  405.  
  406.         canGoSlow = true;
  407.             /* There is a slow-zone around the TextEdit area for slow extend-select.
  408.             ** Allow this zone to slow down selection. */
  409.  
  410.         TEClick(mouseLoc, extendSelect, te);
  411.             /* Do the extend-select thing.  Most of the work is handled by
  412.             ** TextEdit.  The only thing we have to do is to update the
  413.             ** scrollbars while the user is extending the select.  This is
  414.             ** taken care of by our custom clikLoop procedure.
  415.             */
  416.  
  417.         if (viewCtl = CTEViewFromTE(te)) {
  418.             teData = (CTEDataHndl)(*viewCtl)->contrlData;
  419.             (*teData)->newUndo = true;
  420.         }
  421.  
  422.         SetPort(oldPort);
  423.         return(true);
  424.     }
  425.  
  426. /* We didn't hit the view control for a TextEdit record, but don't give up yet.
  427. ** The user may be clicking on a related scrollbar.  Let's find out...
  428. */
  429.  
  430.     if (part = FindControl(mouseLoc, window, &ctlHit)) {
  431.             /* The user did click on a control.  But is it a scrollbar
  432.             ** for a TextEdit control?  Stay tuned...
  433.             */
  434.         te = CTEFromScroll(ctlHit, &viewCtl);
  435.  
  436.         if (te) {        /* It was a related scrollbar. */
  437.  
  438.             if (te != gActiveTEHndl) {
  439.                 CTEActivate(true, te);
  440.                 SetPort(oldPort);
  441.                 return(true);
  442.                     /* If user clicked on TextEdit control other than the
  443.                     ** currently active control, then activate it.  This is our
  444.                     ** only action in this case.
  445.                     */
  446.             }
  447.  
  448.             CTEActivate(true, te);
  449.  
  450.             vert = ((*ctlHit)->contrlRect.top <= (*viewCtl)->contrlRect.top);
  451.                 /* Horizontal or vertical scroll.  Only the rect knows. */
  452.  
  453.             switch (part) {
  454.                 case inThumb:
  455.                     value = GetCtlValue(ctlHit);
  456.                     part = TrackControl(ctlHit, mouseLoc, nil);
  457.                     if (part) {
  458.                         value -= GetCtlValue(ctlHit);
  459.                             /* Value now has CHANGE in value.
  460.                             ** if value changed, scroll. */
  461.                         if (value) {
  462.                             if (vert)
  463.                                 TEScroll(0, value, te);
  464.                             else
  465.                                 TEScroll(value, 0, te);
  466.                         }
  467.                     }
  468.                     break;
  469.  
  470.                 default:
  471.                     teActive = gActiveTEHndl;
  472.                     gActiveTEHndl = te;
  473.                         /* This is a hack way to pass the action procedure
  474.                         ** which TextEdit record we are dealing with.
  475.                         */
  476.                     if (vert)
  477.                         TrackControl(ctlHit, mouseLoc, (ProcPtr)VActionProc);
  478.                     else
  479.                         TrackControl(ctlHit, mouseLoc, (ProcPtr)HActionProc);
  480.  
  481.                     gActiveTEHndl = teActive;
  482.                         /* Unhack our previous hack. */
  483.                     break;
  484.             }
  485.             SetPort(oldPort);
  486.             return(true);
  487.         }
  488.     }
  489.  
  490.     SetPort(oldPort);
  491.     return(false);
  492. }
  493.  
  494.  
  495.  
  496. /*****************************************************************************/
  497.  
  498.  
  499.  
  500. /* This is the custom clikLoop, which is called from the assembly glue code.
  501. ** This handles updating the scrollbars as the user is drag-selecting in
  502. ** the TextEdit control.
  503. */
  504.  
  505. #pragma segment Controls
  506. void    CTEClikLoop(void)
  507. {
  508.     WindowPtr        oldPort, window;
  509.     TEHandle        te;
  510.     Point            mouseLoc;
  511.     Rect            viewRct;
  512.     RgnHandle        rgn;
  513.     short            dl, dr, dt, db, lh;
  514.     long            tick;
  515.  
  516.     GetPort(&oldPort);
  517.     SetPort(window = (WindowPtr)(*gActiveTEHndl)->inPort);
  518.  
  519.     te = gActiveTEHndl;
  520.         /* This better be what the user is dragging, or we did something
  521.         ** wrong elsewhere.
  522.         */
  523.  
  524.     GetMouse(&mouseLoc);
  525.     viewRct = (*te)->viewRect;
  526.  
  527.     if (!PtInRect(mouseLoc, &viewRct)) {
  528.         /* User is outside the TextEdit area, so scrolling is happening. */
  529.  
  530.         if (tick = canGoSlow) tick = TickCount();
  531.             /* As an extra feature, there is a zone around the TextEdit
  532.             ** viewRect that the scroll will be slowed down.  This zone
  533.             ** is based on the lineHeight of the active TextEdit control.
  534.             ** If the user drags outside the viewRect further than the
  535.             ** value of lineHeight, then scrolling occurs as fast as possible.
  536.             */
  537.  
  538.         rgn = NewRgn();
  539.         GetClip(rgn);
  540.         ClipRect(&(window->portRect));
  541.             /* The clipRgn is set to protect everything outside viewRect.
  542.             ** This doesn't work very well when we want to change
  543.             ** the scrollbars.  Save the old and open it up.
  544.             */
  545.  
  546.         dl = viewRct.left - mouseLoc.h;
  547.         dr = mouseLoc.h   - viewRct.right;
  548.         dt = viewRct.top  - mouseLoc.v;
  549.         db = mouseLoc.v   - viewRct.bottom;
  550.             /* Check the delta value for each side of viewRect.  This will
  551.             ** be used to determine if we should scroll fast or slow.
  552.             */
  553.  
  554.         AdjustScrollValues(te);
  555.             /* Scroll them puppies. */
  556.  
  557.         SetClip(rgn);                                /* restore clip */
  558.         DisposeRgn(rgn);
  559.             /* Make Mr. clipRgn happy again. */
  560.  
  561.         lh = (*te)->lineHeight;
  562.         if ((dl < lh) && (dr < lh) && (dt < lh) && (db < lh))
  563.             while (TickCount() <= tick + 9);
  564.                 /* Do it really slow.  (This is important on an fx!!) */
  565.     }
  566.  
  567.     SetPort(oldPort);
  568. }
  569.  
  570.  
  571.  
  572. /*****************************************************************************/
  573.  
  574.  
  575.  
  576. /* Do the cut/copy/paste/clear operations for the currently active
  577. ** TextEdit control.  Caller assumes appropriateness of the call.  Typically,
  578. ** this routine won't be called at an inappropriate time, since the menu
  579. ** item should be enabled or disabled correctly.
  580. ** Use CTEEditMenu to set the menu items undo/cut/copy/paste/clear correctly
  581. ** for the active TextEdit control.  Since undo isn't currently supported,
  582. ** all that CTEEditMenu does for the undo case is to deactivate it right now.
  583. */
  584.  
  585. #pragma segment Controls
  586. void    CTEClipboard(short menuID)
  587. {
  588.     WindowPtr        oldPort;
  589.     TEHandle        te;
  590.     ControlHandle    viewCtl;
  591.     short            maxTextLen, charsToAdd;
  592.  
  593.     if (!(te = gActiveTEHndl)) return;
  594.     if (!(viewCtl = CTEViewFromTE(te))) return;
  595.  
  596.     GetPort(&oldPort);
  597.     SetPort((*te)->inPort);
  598.     switch (menuID) {
  599.         case 2:
  600.             CTENewUndo(viewCtl, true);
  601.             TECut(te);
  602.             ZeroScrap();
  603.             if (TEToScrap()) ZeroScrap();
  604.             break;
  605.         case 3:
  606.             TECopy(te);
  607.             ZeroScrap();
  608.             if (TEToScrap()) ZeroScrap();
  609.             break;
  610.         case 4:
  611.             TEFromScrap();
  612.             if (viewCtl) {
  613.                 maxTextLen = (*(CTEDataHndl)((*viewCtl)->contrlData))->maxTextLen;
  614.                 charsToAdd = TEGetScrapLen() - ((*te)->selEnd - (*te)->selStart);
  615.                 if ((*te)->teLength + charsToAdd <= maxTextLen) {
  616.                     CTENewUndo(viewCtl, true);
  617.                     TEPaste(te);
  618.                 }
  619.             }
  620.             break;
  621.         case 5:
  622.             CTENewUndo(viewCtl, true);
  623.             TEDelete(te);
  624.             break;
  625.     }
  626.  
  627.     AdjustTEBottom(te);
  628.     AdjustScrollValues(te);
  629.     SetPort(oldPort);
  630. }
  631.  
  632.  
  633.  
  634. /*****************************************************************************/
  635.  
  636.  
  637.  
  638. #pragma segment Controls
  639. pascal long    CTECtl(short varCode, ControlHandle ctl, short msg, long parm)
  640. {
  641. #pragma unused (varCode)
  642.  
  643.     Rect            viewRct;
  644.     TEHandle        te;
  645.     CTEDataHndl        teData;
  646.     ControlHandle    scrollCtl;
  647.     short            i;
  648.  
  649.     if (te = (TEHandle)GetCRefCon(ctl)) viewRct = (*ctl)->contrlRect;
  650.     else SetRect(&viewRct, 0, 0, 0, 0);
  651.  
  652.     switch (msg) {
  653.         case drawCntl:
  654.             CTEUpdate(te, ctl, false);
  655.             break;
  656.  
  657.         case testCntl:
  658.             if (PtInRect(*(Point *)&parm, &viewRct)) {
  659.                 gFoundViewCtl = ctl;
  660.                 gFoundTEHndl  = te;
  661.                 return(1);
  662.             }
  663.             return(0);
  664.             break;
  665.  
  666.         case calcCRgns:
  667.         case calcCntlRgn:
  668.             if (msg == calcCRgns) parm &= 0x00FFFFFF;
  669.             RectRgn((RgnHandle)parm, &viewRct);
  670.             break;
  671.  
  672.         case initCntl:
  673.             break;
  674.  
  675.         case dispCntl:
  676.             if (te) {
  677.                 if (te == gActiveTEHndl) gActiveTEHndl = nil;
  678.                 TEDispose(te);
  679.                 if (teData = (CTEDataHndl)(*ctl)->contrlData) {
  680.                     if ((*teData)->undoText) DisposHandle((Handle)(*teData)->undoText);
  681.                     DisposHandle((Handle)teData);
  682.                 }
  683.                 for (i = 0; i < 2; ++i)
  684.                     if (scrollCtl = CTEScrollFromView(ctl, i))
  685.                         DisposeControl(scrollCtl);
  686.             }
  687.             break;
  688.  
  689.         case posCntl:
  690.             break;
  691.  
  692.         case thumbCntl:
  693.             break;
  694.  
  695.         case dragCntl:
  696.             break;
  697.  
  698.         case autoTrack:
  699.             break;
  700.     }
  701.  
  702.     return(0);
  703. }
  704.  
  705.  
  706.  
  707. /*****************************************************************************/
  708.  
  709.  
  710.  
  711. #pragma segment Controls
  712. ControlHandle    CTECtlHit(void)
  713. {
  714.     ControlHandle    ctl;
  715.  
  716.     ctl = gFoundViewCtl;
  717.     gFoundViewCtl = nil;
  718.     return(ctl);
  719. }
  720.  
  721.  
  722.  
  723. /*****************************************************************************/
  724.  
  725.  
  726.  
  727. #pragma segment Controls
  728. void    CTEDispose(TEHandle teHndl)
  729. {
  730.     WindowPtr    oldPort;
  731.  
  732.     if (teHndl) {
  733.         GetPort(&oldPort);
  734.         SetPort((*teHndl)->inPort);
  735.         TEDispose(CTEDisposeView(CTEViewFromTE(teHndl)));
  736.             /* Dispose of the TextEdit control completely.  This includes
  737.             ** scrollbars, as well as the TextEdit view control.
  738.             */
  739.         SetPort(oldPort);
  740.     }
  741. }
  742.  
  743.  
  744.  
  745. /*****************************************************************************/
  746.  
  747.  
  748.  
  749. /* Dispose of the view control and related scrollbars.  This function also
  750. ** returns the handle to the TextEdit record, since it was just orphaned.
  751. ** Use this function if you want to get rid of a TextEdit control, but you
  752. ** want to keep the TextEdit record.
  753. */
  754.  
  755. #pragma segment Controls
  756. TEHandle    CTEDisposeView(ControlHandle viewCtl)
  757. {
  758.     TEHandle        te;
  759.     short            vert;
  760.     ControlHandle    ctl;
  761.  
  762.     if (!viewCtl) return(nil);
  763.  
  764.     te = (TEHandle)GetCRefCon(viewCtl);
  765.     SetCRefCon(viewCtl, (long)nil);
  766.  
  767.     for (vert = 0; vert < 2; ++vert)
  768.         if (ctl = CTEScrollFromView(viewCtl, vert))
  769.             DisposeControl(ctl);
  770.  
  771.     DisposeControl(viewCtl);
  772.     return(te);
  773. }
  774.  
  775.  
  776.  
  777. /*****************************************************************************/
  778.  
  779.  
  780.  
  781. #pragma segment Controls
  782. short    CTEDocHeight(TEHandle teHndl)
  783. {
  784.     if (!teHndl) return(0);
  785.  
  786.     return(CTENumTextLines(teHndl) * (*teHndl)->lineHeight);
  787. }
  788.  
  789.  
  790.  
  791. /*****************************************************************************/
  792.  
  793.  
  794.  
  795. /* Enable or disable edit menu items based on the active TextEdit control.
  796. ** You pass the menu ID of the undo item in undoID, and the menu ID of the
  797. ** cut item in cutID.  If undoID or cutID is non-zero, then some action is
  798. ** performed.  Since I don't support TextEdit control undo yet (next version?),
  799. ** all that passing a non-zero value for undoID does is disable the undo
  800. ** menu item.  If you pass a non-zero value for cutID, then the other menu
  801. ** items cut/copy/paste/clear are updated to reflect the status of the
  802. ** active TextEdit control.
  803. */
  804.  
  805. #pragma segment Controls
  806. Boolean    CTEEditMenu(Boolean *activeItem, short editMenu, short undoID, short cutID)
  807. {
  808.     TEHandle        te;
  809.     MenuHandle        menu;
  810.     Boolean            active;
  811.     ControlHandle    viewCtl;
  812.     CTEDataHndl        teData;
  813.  
  814.     *activeItem = active = false;
  815.     menu = GetMHandle(editMenu);
  816.  
  817.     if (undoID)
  818.         DisableItem(menu, undoID);
  819.     if (cutID) {
  820.         DisableItem(menu, cutID);            /* Disable cut. */
  821.         DisableItem(menu, cutID + 1);        /* Disable copy. */
  822.         DisableItem(menu, cutID + 2);        /* Disable paste. */
  823.         DisableItem(menu, cutID + 3);        /* Disable clear. */
  824.     }
  825.  
  826.     if (!(te = gActiveTEHndl)) return(false);
  827.  
  828.     if (undoID) {
  829.         if (viewCtl = CTEViewFromTE(te)) {
  830.             teData = (CTEDataHndl)(*viewCtl)->contrlData;
  831.             if ((*teData)->undoText) {
  832.                 EnableItem(menu, undoID);
  833.                 active = true;
  834.             }
  835.         }
  836.     }
  837.  
  838.     if (cutID) {
  839.         if ((*te)->selStart != (*te)->selEnd) {
  840.             if (!CTEReadOnly(te)) {
  841.                 EnableItem(menu, cutID);        /* Enable cut. */
  842.                 EnableItem(menu, cutID + 3);    /* Enable clear. */
  843.             }
  844.             active = true;
  845.             EnableItem(menu, cutID + 1);        /* Enable copy. */
  846.         }
  847.         if (!CTEReadOnly(te)) {
  848.             TEFromScrap();
  849.             if (TEGetScrapLen()) {
  850.                 active = true;
  851.                 EnableItem(menu, cutID + 2);        /* Enable paste. */
  852.             }
  853.         }
  854.     }
  855.  
  856.     *activeItem = active;
  857.     return(true);
  858. }
  859.  
  860.  
  861.  
  862. /*****************************************************************************/
  863.  
  864.  
  865.  
  866. /* Handle the event if it applies to the active TextEdit control.  If some
  867. ** action occured due to the event, return true.
  868. */
  869.  
  870. #pragma segment Controls
  871. Boolean    CTEEvent(EventRecord *event)
  872. {
  873.     WindowPtr    window;
  874.  
  875.     switch(event->what) {
  876.  
  877.         case mouseDown:
  878.             if (FindWindow(event->where, &window) == inContent)
  879.                 if (window == FrontWindow())
  880.                     return(CTEClick(event));
  881.             break;
  882.  
  883.         case autoKey:
  884.         case keyDown:
  885.             if (!(event->modifiers & cmdKey))
  886.                 return(CTEKey(event));
  887.             break;
  888.     }
  889.  
  890.     return(false);
  891. }
  892.  
  893.  
  894.  
  895. /*****************************************************************************/
  896.  
  897.  
  898.  
  899. /* This determines if a TextEdit control was clicked on directly.  This does
  900. ** not determine if a related scrollbar was clicked on.  If a TextEdit
  901. ** control was clicked on, then true is returned, as well as the TextEdit
  902. ** handle and the handle to the view control.
  903. */
  904.  
  905. #pragma segment Controls
  906. Boolean    CTEFindCtl(WindowPtr window, EventRecord *event,
  907.                    TEHandle *teHndl, ControlHandle *ctlHit)
  908. {
  909.     WindowPtr        oldPort;
  910.     Point            mouseLoc;
  911.  
  912.     if (window) {
  913.         GetPort(&oldPort);
  914.         SetPort(window);
  915.         mouseLoc = event->where;
  916.         GlobalToLocal(&mouseLoc);
  917.         SetPort(oldPort);
  918.  
  919.         gFoundTEHndl = nil;
  920.         FindControl(mouseLoc, window, ctlHit);
  921.         if (*teHndl = gFoundTEHndl) return(true);
  922.     }
  923.  
  924.     *ctlHit = nil;
  925.     return(false);
  926. }
  927.  
  928.  
  929.  
  930. /*****************************************************************************/
  931.  
  932.  
  933.  
  934. #pragma segment Controls
  935. TEHandle    CTEFindActive(WindowPtr window)
  936. {
  937.     if (!window) return(gActiveTEHndl);
  938.         /* User wants whatever is active one, for whatever window. */
  939.  
  940.     if (!gActiveTEHndl) return(nil);
  941.     if (window != (*gActiveTEHndl)->inPort) return(nil);
  942.     return(gActiveTEHndl);
  943. }
  944.  
  945.  
  946.  
  947. /*****************************************************************************/
  948.  
  949.  
  950.  
  951. /* Find the TextEdit record that is related to the indicated scrollbar. */
  952.  
  953. #pragma segment Controls
  954. TEHandle    CTEFromScroll(ControlHandle scrollCtl, ControlHandle *retCtl)
  955. {
  956.     WindowPtr        window;
  957.     ControlHandle    viewCtl;
  958.     TEHandle        te;
  959.  
  960.     if (!IsScrollBar(scrollCtl)) {
  961.         *retCtl = nil;
  962.         return(nil);
  963.     }
  964.  
  965.     window = (*scrollCtl)->contrlOwner;
  966.  
  967.     for (*retCtl = viewCtl = nil;;) {
  968.         viewCtl = CTENext(window, &te, viewCtl);
  969.         if (!viewCtl) return(nil);
  970.         if (viewCtl == (ControlHandle)GetCRefCon(scrollCtl)) {
  971.             *retCtl = viewCtl;
  972.             return(te);
  973.         }
  974.     }
  975. }
  976.  
  977.  
  978.  
  979. /*****************************************************************************/
  980.  
  981.  
  982.  
  983. #pragma segment Controls
  984. void    CTEHide(TEHandle teHndl)
  985. {
  986.     ControlHandle    viewCtl, scrollCtl;
  987.     short            i;
  988.  
  989.     if (teHndl) {
  990.         CTEActivate(false, teHndl);
  991.         viewCtl = CTEViewFromTE(teHndl);
  992.         if (viewCtl) {
  993.             HideControl(viewCtl);
  994.             for (i = 0; i < 2; i++) {
  995.                 scrollCtl = CTEScrollFromView(viewCtl, i);
  996.                 if (scrollCtl) HideControl(scrollCtl);
  997.             }
  998.         }
  999.     }
  1000. }
  1001.  
  1002.  
  1003.  
  1004. /*****************************************************************************/
  1005.  
  1006.  
  1007.  
  1008. /* Blink the caret in the active TextEdit control.  The active TextEdit
  1009. ** control may be read-only, in which case the caret does not blink. */
  1010.  
  1011. #pragma segment Controls
  1012. void    CTEIdle(void)
  1013. {
  1014.     WindowPtr    window;
  1015.  
  1016.     if (gActiveTEHndl)
  1017.         if (window = FrontWindow())
  1018.             if (((WindowPeek)window)->windowKind >= userKind)
  1019.                 TEIdle(gActiveTEHndl);
  1020. }
  1021.  
  1022.  
  1023.  
  1024. /*****************************************************************************/
  1025.  
  1026.  
  1027.  
  1028. /* See if the keypress event applies to the TextEdit control, and if it does,
  1029. ** handle it and return true.
  1030. */
  1031.  
  1032. #pragma segment Controls
  1033. short    CTEKey(EventRecord *event)
  1034. {
  1035.     TEHandle        te;
  1036.     ControlHandle    viewCtl;
  1037.     short            maxTextLen, selStart, selEnd;
  1038.     short            textSelected, arrowKey, lineHeight, retval;
  1039.     char            key;
  1040.     CTEDataHndl        teData;
  1041.     Point            pt;
  1042. #ifdef THINK_C
  1043.     long            tempLong;
  1044. #endif
  1045.  
  1046.     if (!(te = gActiveTEHndl))          return(0);
  1047.     if (CTEReadOnly(te))                return(0);
  1048.     if (!(viewCtl = CTEViewFromTE(te))) return(0);
  1049.  
  1050.     teData     = (CTEDataHndl)(*viewCtl)->contrlData;
  1051.     maxTextLen = (*teData)->maxTextLen;
  1052.     key        = event->message & charCodeMask;
  1053.     selStart   = (*te)->selStart;
  1054.     selEnd     = (*te)->selEnd;
  1055.     if (selStart > selEnd) {
  1056.         selStart = selEnd;
  1057.         selEnd = (*te)->selStart;
  1058.     }
  1059.     textSelected = (selStart != selEnd);
  1060.     arrowKey     = ((key >= kLeftArrow) && (key <= kDownArrow));
  1061.     lineHeight   = (*te)->lineHeight;
  1062.  
  1063.     retval = 1;
  1064.     if (
  1065.         (textSelected) ||                /* If selection range to be replaced or */
  1066.         (arrowKey) ||                    /* key is an arrow or                    */
  1067.         (key == 8) ||                    /* key is a delete or                   */
  1068.         ((*te)->teLength < maxTextLen)    /* we have space for the key...         */
  1069.     ) {
  1070.         CTENewUndo(viewCtl, false);        /* Add the character. */
  1071.         if (arrowKey) {
  1072.             if (event->modifiers & shiftKey) {
  1073.                 switch (key) {
  1074.                     case kUpArrow:
  1075. #ifndef THINK_PRE_5
  1076.                         pt       = TEGetPoint(selStart, te);
  1077. #else
  1078.                         tempLong = TEGetPoint(selStart, te);
  1079.                         BlockMove(&tempLong, &pt, 4);
  1080. #endif
  1081.                         pt.v -= (lineHeight >> 1);
  1082.                         if (selStart == TEGetOffset(pt, te)) pt.v -= lineHeight;
  1083.                         selStart = TEGetOffset(pt, te);
  1084.                         CTEFakeClick(selStart, selEnd, true, te);
  1085.                         break;
  1086.                     case kDownArrow:
  1087. #ifndef THINK_PRE_5
  1088.                         pt       = TEGetPoint(selEnd, te);
  1089. #else
  1090.                         tempLong = TEGetPoint(selEnd, te);
  1091.                         BlockMove(&tempLong, &pt, 4);
  1092. #endif
  1093.                         selEnd = TEGetOffset(pt, te);
  1094.                         CTEFakeClick(selStart, selEnd, true, te);
  1095.                         break;
  1096.                     case kLeftArrow:
  1097.                         CTEFakeClick(--selStart, selEnd, true, te);
  1098.                         break;
  1099.                     case kRightArrow:
  1100.                         CTEFakeClick(selStart, ++selEnd, true, te);
  1101.                         break;
  1102.                 }
  1103.                 AdjustTEBottom(te);
  1104.                 AdjustScrollValues(te);
  1105.                 return(retval);
  1106.             }
  1107.             if (textSelected) {
  1108.                 switch (key) {
  1109.                     case kUpArrow:
  1110.                     case kLeftArrow:
  1111.                         TESetSelect(selStart, selStart, te);
  1112.                         if (key == kUpArrow) textSelected = false;
  1113.                         break;
  1114.                     case kRightArrow:
  1115.                     case kDownArrow:
  1116.                         TESetSelect(selEnd, selEnd, te);
  1117.                         if (key == kDownArrow) textSelected = false;
  1118.                         break;
  1119.                 }
  1120.                 if (textSelected) {
  1121.                     AdjustTEBottom(te);
  1122.                     AdjustScrollValues(te);
  1123.                     return(retval);
  1124.                 }
  1125.             }
  1126.         }
  1127.  
  1128.         switch (key) {
  1129.             case kUpArrow:
  1130. #ifndef THINK_PRE_5
  1131.                 pt       = TEGetPoint(selStart, te);
  1132. #else
  1133.                 tempLong = TEGetPoint(selStart, te);
  1134.                 BlockMove(&tempLong, &pt, 4);
  1135. #endif
  1136.                 pt.v -= (lineHeight >> 1);
  1137.                 if (selStart == TEGetOffset(pt, te)) pt.v -= lineHeight;
  1138.                 selStart = TEGetOffset(pt, te);
  1139.                 CTEFakeClick(selStart, selStart, false, te);
  1140.                 break;
  1141.             case kLeftArrow:
  1142.                 CTEFakeClick(--selStart, selEnd, false, te);
  1143.                 break;
  1144.             case kRightArrow:
  1145.                 CTEFakeClick(selStart, ++selEnd, false, te);
  1146.                 break;
  1147.             default:
  1148.                 TEKey(key, te);
  1149.                 if (key != kDownArrow) ++retval;
  1150.                 break;
  1151.         }
  1152.  
  1153.         AdjustTEBottom(te);
  1154.         AdjustScrollValues(te);
  1155.     }
  1156.  
  1157.     return(retval);
  1158. }
  1159.  
  1160.  
  1161.  
  1162. /*****************************************************************************/
  1163.  
  1164.  
  1165.  
  1166. /* This function is used to move a TextEdit control.  Pass it the TextEdit
  1167. ** record to move, plus the new position.  It will move the TextEdit control,
  1168. ** along with any scrollbars the control may have.  All areas that need
  1169. ** updating are cleared and invalidated.
  1170. */
  1171.  
  1172. #pragma segment Controls
  1173. void    CTEMove(TEHandle teHndl, short newH, short newV)
  1174. {
  1175.     WindowPtr        oldPort;
  1176.     Rect            viewRct, brdrRct, rct;
  1177.     short            i, dx, dy, mode;
  1178.     ControlHandle    viewCtl, ctl;
  1179.     CTEDataHndl        teData;
  1180.  
  1181.     if (!(viewCtl = CTEViewFromTE(teHndl))) return;
  1182.  
  1183.     GetPort(&oldPort);
  1184.     SetPort((*teHndl)->inPort);
  1185.     teData = (CTEDataHndl)(*viewCtl)->contrlData;
  1186.  
  1187.     viewRct = (*viewCtl)->contrlRect;
  1188.     EraseRect(&viewRct);
  1189.     InvalRect(&rct);
  1190.     brdrRct = (*teData)->brdrRect;
  1191.     EraseRect(&brdrRct);
  1192.     InvalRect(&brdrRct);
  1193.  
  1194.     dx = newH - viewRct.left;
  1195.     dy = newV - viewRct.top;
  1196.  
  1197.     for (i = 0; i < 2; ++i) {
  1198.         if (ctl = CTEScrollFromView(viewCtl, i)) {
  1199.             rct = (*ctl)->contrlRect;
  1200.             MoveControl(ctl, rct.left + dx, rct.top + dy);
  1201.         }
  1202.     }
  1203.  
  1204.     OffsetRect(&(*viewCtl)->contrlRect, dx, dy);
  1205.     OffsetRect(&(*teHndl)->destRect, dx, dy);
  1206.     OffsetRect(&(*teHndl)->viewRect, dx, dy);
  1207.     OffsetRect(&(*teData)->brdrRect, dx, dy);
  1208.  
  1209.     rct = viewRct;
  1210.     OffsetRect(&rct, dx, dy);
  1211.  
  1212.     mode = (*teData)->mode;
  1213.     rct.top  = rct.bottom - 16;
  1214.     rct.left = rct.right - 16;
  1215.  
  1216.     if (mode & (cteVScrollLessGrow - cteVScroll + cteHScrollLessGrow - cteHScroll))
  1217.         EraseRect(&rct);
  1218.  
  1219.     if (mode & (cteVScrollLessGrow - cteVScroll)) OffsetRect(&rct, 16, 0);
  1220.     if (mode & (cteHScrollLessGrow - cteHScroll)) OffsetRect(&rct, 0, 16);
  1221.     InvalRect(&rct);
  1222.  
  1223.     SetPort(oldPort);
  1224. }
  1225.  
  1226.  
  1227.  
  1228. /*****************************************************************************/
  1229.  
  1230.  
  1231.  
  1232. /* Create a new TextEdit control.  See the comments at the beginning of this
  1233. ** file for more information.
  1234. */
  1235.  
  1236. #pragma segment Controls
  1237. void    CTENew(short viewID, WindowPtr window, TEHandle *teHndl, Rect *cRect, Rect *dRect,
  1238.                Rect *vRect, Rect *bRect, short maxTextLen, short mode)
  1239. {
  1240.     Rect            destRect, viewRect, brdrRect;
  1241.     WindowPtr        oldPort;
  1242.     TEHandle        te;
  1243.     Boolean            err;
  1244.     short            width, height;
  1245.     Rect            ctlRect;
  1246.     ControlHandle    viewCtl, hScrollCtl, vScrollCtl;
  1247.     cdefRsrcJMPHndl    cdefRsrc;
  1248.     CTEDataHndl        teData;
  1249.  
  1250.     theViewID = viewID;        /* Keep viewID that was passed in. */
  1251.  
  1252.     GetPort(&oldPort);
  1253.     SetPort(window);
  1254.  
  1255.     destRect = *dRect;
  1256.     viewRect = *vRect;
  1257.     brdrRect = *bRect;
  1258.         /* Make sure that the rects are not in memory that may move. */
  1259.  
  1260.     te = TENew(&destRect, &viewRect);
  1261.         /* Do the main thing. */
  1262.  
  1263.     err = false;
  1264.     viewCtl = hScrollCtl = vScrollCtl = nil;
  1265.         /* Prepare for various failures. */
  1266.  
  1267.     if (te) {        /* If we were able to create the TextEdit record... */
  1268.  
  1269.         TEAutoView(true, te);
  1270.             /* Let TextEdit 3.0 do most of the scrolling work. */
  1271.         gDefaultClikLoop = (*te)->clikLoop;
  1272.         (*te)->clikLoop  = ASMTECLIKLOOP;
  1273.             /* We will do the remainder of the work. */
  1274.  
  1275.         cdefRsrc = (cdefRsrcJMPHndl)GetResource('CDEF', viewID);
  1276.         (*cdefRsrc)->jmpAddress = (long)CTECtl;
  1277.         FlushInstructionCache();
  1278.             /* Make sure that instruction caches don't kill us. */
  1279.  
  1280.         viewCtl = NewControl(window, cRect, nil, false, 0, 0, 0,
  1281.                              viewID * 16, (long)te);
  1282.             /* Use our custom view cdef.  It's wierd, but it's small. */
  1283.  
  1284.         if (!viewCtl) err = true;
  1285.         else {
  1286.             (*viewCtl)->contrlData = nil;
  1287.             if (teData = (CTEDataHndl)NewHandle(sizeof(CTEDataRec))) {
  1288.                 (*teData)->maxTextLen  = maxTextLen;
  1289.                 (*teData)->undoText    = nil;
  1290.                 (*teData)->mode        = mode;
  1291.                 (*teData)->brdrRect    = brdrRect;
  1292.                 (*viewCtl)->contrlData = (Handle)teData;
  1293.             }
  1294.             else err = true;
  1295.  
  1296.             if (mode & cteHScroll) {        /* Caller wants a horizontal scrollbar... */
  1297.                 SetRect(&ctlRect, 0, 0, 100, 16);
  1298.                 hScrollCtl = NewControl(window, &ctlRect, nil, true, 0, 0, 0,
  1299.                                         scrollBarProc, (long)viewCtl);
  1300.                 if (hScrollCtl) {
  1301.                     MoveControl(hScrollCtl, brdrRect.left, brdrRect.bottom - 1);
  1302.                     width = brdrRect.right - brdrRect.left;
  1303.                     if (mode & (cteHScrollLessGrow - cteHScroll))
  1304.                         if (!(mode & cteVScroll)) width -= 15;
  1305.                     SizeControl(hScrollCtl, width, 16);
  1306.                         /* Line the scrollbar up with the borderRect. */
  1307.                 }
  1308.                 else err = true;
  1309.             }
  1310.  
  1311.             if (mode & cteVScroll) {        /* Caller wants a vertical scrollbar... */
  1312.                 SetRect(&ctlRect, 0, 0, 16, 100);
  1313.                 vScrollCtl = NewControl(window, &ctlRect, nil, true, 0, 0, 0,
  1314.                                         scrollBarProc, (long)viewCtl);
  1315.                 if (vScrollCtl) {
  1316.                     MoveControl(vScrollCtl, brdrRect.right - 1, brdrRect.top);
  1317.                     height = brdrRect.bottom - brdrRect.top;
  1318.                     if (mode & (cteVScrollLessGrow - cteVScroll))
  1319.                         if (!(mode & cteHScroll)) height -= 15;
  1320.                     SizeControl(vScrollCtl, 16, height);
  1321.                         /* Line the scrollbar up with the borderRect. */
  1322.                 }
  1323.                 else err = true;
  1324.             }
  1325.         }
  1326.     }
  1327.     else err = true;
  1328.  
  1329.     SetPort(oldPort);
  1330.  
  1331.     if (err) {        /* Oops.  Somebody wasn't happy. */
  1332.         if (viewCtl)
  1333.             DisposeControl(viewCtl);
  1334.                 /* This also disposes of TextEdit handle! */
  1335.         else
  1336.             if (te) TEDispose(te);
  1337.                 /* We have to dispose of the TextEdit handle ourselves if
  1338.                 ** creating the view control failed. */
  1339.  
  1340.         te = nil;        /* Return that there is no TextEdit control. */
  1341.  
  1342.         if (hScrollCtl)
  1343.             DisposeControl(hScrollCtl);
  1344.                 /* More clean-up. */
  1345.  
  1346.         if (vScrollCtl)
  1347.             DisposeControl(vScrollCtl);
  1348.                 /* And still more clean-up. */
  1349.  
  1350.         *teHndl = nil;        /* Return that we failed. */
  1351.     }
  1352.     else {
  1353.         ShowControl(viewCtl);        /* Since everything worked, show the control. */
  1354.  
  1355.         if (mode & cteReadOnly)
  1356.             (*te)->caretHook = (ProcPtr)ASMNOCARET;
  1357.                 /* If read-only, then disable caret for this
  1358.                 ** TextEdit control.
  1359.                 */
  1360.  
  1361.         if (mode & cteActive) CTEActivate(true, te);
  1362.  
  1363.         AdjustScrollValues(*teHndl = te);
  1364.             /* Give the scrollbars an initial value.  This is because the
  1365.             ** TextEdit control could have been created with
  1366.             ** destRect.top < viewRect.top or destRect.left < viewRect.left.
  1367.             ** I don't know why anyone would want to, but it is legal.
  1368.             */
  1369.     }
  1370. }
  1371.  
  1372.  
  1373.  
  1374. /*****************************************************************************/
  1375.  
  1376.  
  1377.  
  1378. /* Save the data (if appropriate) so that user can undo. */
  1379.  
  1380. #pragma segment Controls
  1381. void    CTENewUndo(ControlHandle viewCtl, Boolean alwaysNewUndo)
  1382. {
  1383.     TEHandle    teHndl;
  1384.     CTEDataHndl    teData;
  1385.     Handle        hText, uText;
  1386.  
  1387.     if (!viewCtl) return;
  1388.  
  1389.     teHndl = (TEHandle)GetCRefCon(viewCtl);
  1390.     teData = (CTEDataHndl)(*viewCtl)->contrlData;
  1391.  
  1392.     hText = (*teHndl)->hText;
  1393.     uText = (*teData)->undoText;
  1394.         /* hText is text in the TextEdit record that is being edited.  */
  1395.         /* uText is the undo data (if any) prior to current edit task. */
  1396.  
  1397.     if (!uText) {                /* No undo text (therefore no editing) yet. */
  1398.         uText = NewHandle((*teHndl)->teLength);
  1399.         if (!uText) return;        /* Not enough memory to support undo. */
  1400.         (*teData)->undoText = uText;
  1401.         alwaysNewUndo = true;
  1402.             /* Since this is our first edit, we will want to cache the
  1403.             ** data, no matter for what reason we were called. */
  1404.     }
  1405.  
  1406.     if ((alwaysNewUndo) || ((*teData)->newUndo)) {
  1407.         SetHandleSize(uText, (*teHndl)->teLength);
  1408.         if (MemError()) return;
  1409.             /* Not enough memory to handle this undo. */
  1410.         BlockMove(*hText, *uText, (*teHndl)->teLength);
  1411.         (*teData)->newUndo      = false;
  1412.         (*teData)->undoSelStart = (*teHndl)->selStart;
  1413.         (*teData)->undoSelEnd   = (*teHndl)->selEnd;
  1414.     }
  1415. }
  1416.  
  1417.  
  1418.  
  1419. /*****************************************************************************/
  1420.  
  1421.  
  1422.  
  1423. /* Get the next TextEdit control in the window.  You pass it a control handle
  1424. ** for the view control, or nil to start at the beginning of the window.
  1425. ** It returns both a TextEdit handle and the view control handle for that
  1426. ** TextEdit record.  If none is found, nil is returned.  This allows you to
  1427. ** repeatedly call this function and walk through all the TextEdit controls
  1428. ** in a window.
  1429. */
  1430.  
  1431. #pragma segment Controls
  1432. ControlHandle    CTENext(WindowPtr window, TEHandle *teHndl, ControlHandle ctl)
  1433. {
  1434.     short    defProcID;
  1435.     ResType    defProcType;
  1436.     Str255    defProcName;
  1437.  
  1438.     *teHndl = nil;
  1439.     if (!window) return(nil);
  1440.  
  1441.     if (!ctl)
  1442.         ctl = ((WindowPeek)window)->controlList;
  1443.     else
  1444.         ctl = (*ctl)->nextControl;
  1445.  
  1446.     while (ctl) {
  1447.         defProcID = !theViewID;
  1448.         GetResInfo((*ctl)->contrlDefProc, &defProcID, &defProcType, defProcName);
  1449.         if (defProcID == theViewID) {
  1450.             *teHndl = (TEHandle)GetCRefCon(ctl);
  1451.             break;
  1452.         }
  1453.         ctl = (*ctl)->nextControl;
  1454.     }
  1455.  
  1456.     return(ctl);
  1457. }
  1458.  
  1459.  
  1460.  
  1461. /*****************************************************************************/
  1462.  
  1463.  
  1464.  
  1465. /* Return the number of lines of text.  This is because there is a bug in
  1466. ** TextEdit where the number of lines returned is incorrect if the text
  1467. ** ends with a c/r.  This function adjusts for this bug.
  1468. */
  1469.  
  1470. #pragma segment Controls
  1471. short    CTENumTextLines(TEHandle teHndl)
  1472. {
  1473.     short    lines;
  1474.     char    *cptr;
  1475.  
  1476.     if (!teHndl) return(0);
  1477.  
  1478.     lines = (*teHndl)->nLines;
  1479.  
  1480.     cptr = *((*teHndl)->hText);        /* Pointer to first TextEdit character. */
  1481.     if (cptr[(*teHndl)->teLength - 1] == kCrChar) ++lines;
  1482.         /* Since nLines isn’t right if the last character is a return,
  1483.         ** check for that case and fix it.
  1484.         */
  1485.  
  1486.     return(lines);
  1487. }
  1488.  
  1489.  
  1490.  
  1491. /*****************************************************************************/
  1492.  
  1493.  
  1494.  
  1495. /* Return the number of text lines in the view area. */
  1496.  
  1497. #pragma segment Controls
  1498. short    CTENumViewLines(TEHandle teHndl)
  1499. {
  1500.     short    viewHeight;
  1501.  
  1502.     if (!teHndl) return(0);
  1503.  
  1504.     viewHeight = (*teHndl)->viewRect.bottom - (*teHndl)->viewRect.top;
  1505.     return(viewHeight / (*teHndl)->lineHeight);
  1506. }
  1507.  
  1508.  
  1509.  
  1510. /*****************************************************************************/
  1511.  
  1512.  
  1513.  
  1514. /* Use this function to print the contents of a TextEdit record.  Pass it a
  1515. ** TextEdit handle, a pointer to a text offset, and a pointer to a rect to
  1516. ** print the text in.  The offset should be initialized to what character
  1517. ** in the TextEdit record you wish to start printing at (most likely 0).
  1518. ** The print function prints as much text as will fit in the rect, and
  1519. ** then updates the offset to tell you what is the first character that didn't
  1520. ** print.  You can then call the print function again with another rect with
  1521. ** this new offset, and it will print the text starting at the new offset.
  1522. ** This method is very useful when a single TextEdit record is longer than a
  1523. ** single page, and you wish the text to break at the end of the page.
  1524. ** The bottom of rect is also updated, along with the offset.  The bottom edge
  1525. ** of the rect is changed to reflect the actual bottom of the text printed.
  1526. ** This is useful because the rect passed in didn't necessarily hold an
  1527. ** integer number of lines of text.  The bottom of the rect is adjusted so
  1528. ** it exactly holds complete lines of text.
  1529. ** It is also possible that the rect could hold substantially more lines of
  1530. ** text than there are remaining.  Again, in this situation, the bottom of
  1531. ** rect is adjusted so that the rect tightly bounds the text printed.
  1532. ** The remaining piece of information passed back is an indicator that the
  1533. ** text through the end of the TextEdit record was printed.  When the end
  1534. ** of the text is reached, the offset for the next text to be printed is
  1535. ** returned as -1.  This indicates that processing of the TextEdit record
  1536. ** is complete.
  1537. */
  1538.  
  1539. #pragma segment Controls
  1540. OSErr    CTEPrint(TEHandle teHndl, short *teOffset, Rect *teRct)
  1541. {
  1542.     short        len, offset, numLines, rctHeight, rctLines, h;
  1543.     Handle        hText, keepHText;
  1544.     Rect        keepDestRect, keepViewRect;
  1545.     WindowPtr    tePort, printPort;
  1546.  
  1547.     if (teHndl) return(noErr);
  1548.  
  1549.     len = (*teHndl)->teLength;
  1550.     if (!(keepHText = NewHandle(len))) return(memFullErr);
  1551.  
  1552.     if ((offset = *teOffset) >= len) {
  1553.         *teOffset = -1;                    /* We are offset further than we have text. */
  1554.         teRct->bottom = teRct->top;        /* Empty rect, since no text to draw.  */
  1555.         return(noErr);                    /* Just say that we have no more text. */
  1556.     }
  1557.  
  1558.     BlockMove(*(hText = (*teHndl)->hText), *keepHText, len);
  1559.     keepDestRect = (*teHndl)->destRect;
  1560.     keepViewRect = (*teHndl)->viewRect;
  1561.     tePort = (*teHndl)->inPort;
  1562.         /* Cache some information from the TextEdit record. */
  1563.  
  1564.     BlockMove(*hText + offset, *hText, len - offset);
  1565.     SetHandleSize(hText, (*teHndl)->teLength = len - offset);
  1566.         /* Throw out the characters that have already been printed. */
  1567.  
  1568.     GetPort(&printPort);
  1569.     (*teHndl)->inPort = printPort;
  1570.     (*teHndl)->destRect = (*teHndl)->viewRect = *teRct;
  1571.     TECalText(teHndl);
  1572.         /* Install the print rect into the TextEdit record and then
  1573.         ** rewrap the unprinted text into this rectangle.  The text
  1574.         ** is now formatted correctly to print this rect's worth. */
  1575.  
  1576.     numLines  = CTENumTextLines(teHndl);
  1577.     rctHeight = teRct->bottom - teRct->top;
  1578.     rctLines  = rctHeight / (h = (*teHndl)->lineHeight);
  1579.  
  1580.     if (rctLines > numLines) rctLines = numLines;
  1581.     teRct->bottom = teRct->top + rctLines * h;
  1582.     (*teHndl)->destRect = (*teHndl)->viewRect = *teRct;
  1583.         /* We now have the minimum rectangle to hold as much of the text
  1584.         ** as would fit into the rectangle passed in.  The final calc on
  1585.         ** the rect prevents partial lines from being drawn. */
  1586.  
  1587.     TEUpdate(teRct, teHndl);        /* Draw this portion of the text. */
  1588.  
  1589.     if (rctLines == numLines)
  1590.         *teOffset = -1;
  1591.             /* Printed the last of the text for this record. */
  1592.     else
  1593.         *teOffset = (*teHndl)->lineStarts[rctLines] + offset;
  1594.             /* Offset to the first character not printed. */
  1595.  
  1596.     SetHandleSize(hText, len);
  1597.     BlockMove(*keepHText, *hText, len);
  1598.     DisposHandle(keepHText);
  1599.     (*teHndl)->teLength = len;
  1600.     (*teHndl)->inPort   = tePort;
  1601.     (*teHndl)->destRect = keepDestRect;
  1602.     (*teHndl)->viewRect = keepViewRect;
  1603.     TECalText(teHndl);        /* Restore the TextEdit record to the way it was. */
  1604.     return(noErr);            /* Everything worked. */
  1605. }
  1606.  
  1607.  
  1608.  
  1609. /*****************************************************************************/
  1610.  
  1611.  
  1612.  
  1613. /* Return if the TextEdit control is read/write (true) or read-only (false). */
  1614.  
  1615. #pragma segment Controls
  1616. Boolean    CTEReadOnly(TEHandle teHndl)
  1617. {
  1618.     if (!teHndl) return(false);
  1619.     if ((*teHndl)->caretHook == ASMNOCARET) return(true);
  1620.     return(false);
  1621. }
  1622.  
  1623.  
  1624.  
  1625. /*****************************************************************************/
  1626.  
  1627.  
  1628.  
  1629. /* Return the control handle for the TextEdit control's scrollbar, either
  1630. ** vertical or horizontal.  If the scrollbar doesn't, nil is returned.
  1631. */
  1632.  
  1633. #pragma segment Controls
  1634. ControlHandle    CTEScrollFromTE(TEHandle teHndl, Boolean vertScroll)
  1635. {
  1636.     ControlHandle    viewCtl;
  1637.  
  1638.     if (!(viewCtl = CTEViewFromTE(teHndl))) return(nil);
  1639.  
  1640.     return(CTEScrollFromView(viewCtl, vertScroll));
  1641. }
  1642.  
  1643.  
  1644.  
  1645. /*****************************************************************************/
  1646.  
  1647.  
  1648.  
  1649. /* Return the control handle for the scrollbar related to the view control,
  1650. ** either horizontal or vertical.  If the scrollbar doesn't exist, return nil.
  1651. */
  1652.  
  1653. #pragma segment Controls
  1654. ControlHandle    CTEScrollFromView(ControlHandle viewCtl, Boolean vertScroll)
  1655. {
  1656.     ControlHandle    ctl;
  1657.     WindowPtr        window;
  1658.     Boolean            vert;
  1659.  
  1660.     if (!viewCtl) return(nil);
  1661.  
  1662.     window = (*viewCtl)->contrlOwner;
  1663.     ctl    = ((WindowPeek)window)->controlList;
  1664.  
  1665.     for (; ctl;) {
  1666.         if ((ControlHandle)GetCRefCon(ctl) == viewCtl) {
  1667.             vert = false;
  1668.             if ((*ctl)->contrlRect.right == (*ctl)->contrlRect.left + 16)
  1669.                 vert = true;
  1670.             if (vert == vertScroll) return(ctl);
  1671.         }
  1672.         ctl = (*ctl)->nextControl;
  1673.     }
  1674.     return(nil);
  1675. }
  1676.  
  1677.  
  1678.  
  1679. /*****************************************************************************/
  1680.  
  1681.  
  1682.  
  1683. /* Select a range of text.  TESetSelect can't be used alone because it doesn't
  1684. ** update the scrollbars.  This function calls TESetSelect, and then fixes up
  1685. ** the scrollbars.
  1686. */
  1687.  
  1688. #pragma segment Controls
  1689. void    CTESetSelect(short start, short end, TEHandle teHndl)
  1690. {
  1691.     WindowPtr        oldPort;
  1692.     ControlHandle    viewCtl;
  1693.     CTEDataHndl        teData;
  1694.  
  1695.     if (teHndl) {
  1696.         GetPort(&oldPort);
  1697.         SetPort((*teHndl)->inPort);
  1698.         TESetSelect(start, end, teHndl);
  1699.         AdjustScrollValues(teHndl);
  1700.         if (viewCtl = CTEViewFromTE(teHndl)) {
  1701.             teData = (CTEDataHndl)(*viewCtl)->contrlData;
  1702.             (*teData)->newUndo = true;
  1703.         }
  1704.         SetPort(oldPort);
  1705.     }
  1706. }
  1707.  
  1708.  
  1709.  
  1710. /*****************************************************************************/
  1711.  
  1712.  
  1713.  
  1714. #pragma segment Controls
  1715. void    CTEShow(TEHandle teHndl)
  1716. {
  1717.     ControlHandle    viewCtl, scrollCtl;
  1718.     short            i;
  1719.  
  1720.     if (viewCtl = CTEViewFromTE(teHndl)) {
  1721.         ShowControl(viewCtl);
  1722.         for (i = 0; i < 2; i++) {
  1723.             scrollCtl = CTEScrollFromView(viewCtl, i);
  1724.             if (scrollCtl) ShowControl(scrollCtl);
  1725.         }
  1726.     }
  1727. }
  1728.  
  1729.  
  1730.  
  1731. /*****************************************************************************/
  1732.  
  1733.  
  1734.  
  1735. /* This function is used to resize a TextEdit control.  Pass it the TextEdit
  1736. ** record to resize, plus the new horizontal and vertical size.  It will
  1737. ** resize the TextEdit control, realign the text, if necessary, plus it will
  1738. ** resize and adjust any scrollbars the TextEdit control may have.  All areas
  1739. ** that need updating are cleared and invalidated.
  1740. */
  1741.  
  1742. #pragma segment Controls
  1743. void    CTESize(TEHandle teHndl, short newH, short newV, Boolean newDest)
  1744. {
  1745.     WindowPtr        oldPort;
  1746.     Rect            viewRct, brdrRct, teViewRct, oldTeViewRct, rct;
  1747.     short            i, dx, dy, mode;
  1748.     ControlHandle    viewCtl, ctl[2];
  1749.     CTEDataHndl        teData;
  1750.     RgnHandle        rgn1, rgn2;
  1751.  
  1752.     if (!(viewCtl = CTEViewFromTE(teHndl))) return;
  1753.  
  1754.     GetPort(&oldPort);
  1755.     SetPort((*teHndl)->inPort);
  1756.  
  1757.     viewRct = (*viewCtl)->contrlRect;
  1758.     teData  = (CTEDataHndl)(*viewCtl)->contrlData;
  1759.     brdrRct = (*teData)->brdrRect;
  1760.     mode    = (*teData)->mode;
  1761.  
  1762.     if (mode & (cteVScrollLessGrow - cteVScroll + cteHScrollLessGrow - cteHScroll)) {
  1763.         rct = viewRct;
  1764.         rct.top  = rct.bottom - 16;
  1765.         rct.left = rct.right - 16;
  1766.         if (mode & (cteVScrollLessGrow - cteVScroll)) OffsetRect(&rct, 16, 0);
  1767.         if (mode & (cteHScrollLessGrow - cteHScroll)) OffsetRect(&rct, 0, 16);
  1768.         EraseRect(&rct);
  1769.         InvalRect(&rct);
  1770.     }        /* Erase the old grow box, if we have one. */
  1771.  
  1772.     dx = newH - (viewRct.right  - viewRct.left);
  1773.     dy = newV - (viewRct.bottom - viewRct.top);
  1774.  
  1775.     for (i = 0; i < 2; ++i) {
  1776.         if (ctl[i] = CTEScrollFromView(viewCtl, i)) {
  1777.             rct = (*ctl[i])->contrlRect;
  1778.             if (i) {
  1779.                 HideControl(ctl[i]);
  1780.                 SizeControl(ctl[i], rct.right - rct.left, rct.bottom - rct.top + dy);
  1781.                 MoveControl(ctl[i], rct.left + dx, rct.top);
  1782.             }
  1783.             else {
  1784.                 HideControl(ctl[i]);
  1785.                 SizeControl(ctl[i], rct.right - rct.left + dx, rct.bottom - rct.top);
  1786.                 MoveControl(ctl[i], rct.left, rct.top + dy);
  1787.             }
  1788.         }
  1789.     }        /* Reposition the scrollbars, if we have any. */
  1790.  
  1791.     InvalRect(&viewRct);        /* Resize our view control. */
  1792.     viewRct.right  += dx;
  1793.     viewRct.bottom += dy;
  1794.     (*viewCtl)->contrlRect = viewRct;
  1795.     InvalRect(&viewRct);
  1796.  
  1797.     InvalRect(&brdrRct);        /* Resize our border rect. */
  1798.     brdrRct.right  += dx;
  1799.     brdrRct.bottom += dy;
  1800.     (*teData)->brdrRect = brdrRct;
  1801.     InvalRect(&brdrRct);
  1802.  
  1803.     teViewRct = oldTeViewRct = (*teHndl)->viewRect;        /* Resize TextEdit viewRect. */
  1804.     teViewRct.right  += dx;
  1805.     teViewRct.bottom += dy;
  1806.     (*teHndl)->viewRect = teViewRct;
  1807.  
  1808.     if (newDest) {        /* Resize TextEdit destRect, if so indicated. */
  1809.         (*teHndl)->destRect.right  += dx;
  1810.         (*teHndl)->destRect.bottom += dy;
  1811.         TECalText(teHndl);
  1812.         EraseRect(&oldTeViewRct);        /* Redraw the whole thing if destRect changes. */
  1813.         InvalRect(&oldTeViewRct);
  1814.     }
  1815.  
  1816.     rgn1 = NewRgn();
  1817.     rgn2 = NewRgn();
  1818.     RectRgn(rgn1, &oldTeViewRct);        /* Clear old areas if we are shrinking. */
  1819.     RectRgn(rgn2, &teViewRct);
  1820.     DiffRgn(rgn1, rgn2, rgn2);
  1821.     EraseRgn(rgn2);
  1822.     InvalRgn(rgn2);
  1823.     RectRgn(rgn2, &teViewRct);            /* Update new areas if we are growing. */
  1824.     DiffRgn(rgn2, rgn1, rgn2);
  1825.     InvalRgn(rgn2);
  1826.     DisposeRgn(rgn1);
  1827.     DisposeRgn(rgn2);
  1828.  
  1829.     for (i = 0; i < 2; ++i) if (ctl[i]) ShowControl(ctl[i]);
  1830.         /* Show the controls in their new position. */
  1831.  
  1832.     if (mode & (cteVScrollLessGrow - cteVScroll + cteHScrollLessGrow - cteHScroll)) {
  1833.         rct = viewRct;
  1834.         rct.top  = rct.bottom - 16;
  1835.         rct.left = rct.right - 16;
  1836.         if (mode & (cteVScrollLessGrow - cteVScroll)) OffsetRect(&rct, 16, 0);
  1837.         if (mode & (cteHScrollLessGrow - cteHScroll)) OffsetRect(&rct, 0, 16);
  1838.         EraseRect(&rct);
  1839.         InvalRect(&rct);
  1840.     }
  1841.  
  1842.     AdjustTEBottom(teHndl);
  1843.     AdjustScrollValues(teHndl);
  1844.  
  1845.     SetPort(oldPort);
  1846. }
  1847.  
  1848.  
  1849.  
  1850. /*****************************************************************************/
  1851.  
  1852.  
  1853.  
  1854. /* Swap the TextEdit text handle with the text handle passed in. */
  1855.  
  1856. #pragma segment Controls
  1857. Handle    CTESwapText(TEHandle teHndl, Handle newText, Boolean update)
  1858. {
  1859.     WindowPtr    oldPort;
  1860.     Handle        hText;
  1861.     Rect        destRect, viewRect;
  1862.     short        oldTextLen, newTextLen;
  1863.  
  1864.     if (!teHndl) return(nil);
  1865.  
  1866.     GetPort(&oldPort);
  1867.     SetPort((*teHndl)->inPort);
  1868.  
  1869.     hText = (*teHndl)->hText;
  1870.  
  1871.     newTextLen = GetHandleSize(newText);
  1872.     oldTextLen = (*teHndl)->teLength;
  1873.     SetHandleSize(hText, oldTextLen);
  1874.         /* Just to make sure TE is behaving itself. */
  1875.  
  1876.     (*teHndl)->hText = newText;
  1877.     (*teHndl)->teLength = newTextLen;
  1878.  
  1879.     TECalText(teHndl);
  1880.  
  1881.     if (update) {
  1882.         destRect = (*teHndl)->destRect;
  1883.         viewRect = (*teHndl)->viewRect;
  1884.  
  1885.         OffsetRect(&destRect,
  1886.             viewRect.left - destRect.left,
  1887.             viewRect.top  - destRect.top);
  1888.  
  1889.         (*teHndl)->destRect = destRect;
  1890.         (*teHndl)->selStart = (*teHndl)->selEnd = 0;
  1891.  
  1892.         EraseRect(&viewRect);
  1893.         TEUpdate(&viewRect, teHndl);
  1894.     }
  1895.  
  1896.     AdjustScrollValues(teHndl);
  1897.  
  1898.     SetPort(oldPort);
  1899.     return(hText);
  1900. }
  1901.  
  1902.  
  1903.  
  1904. /*****************************************************************************/
  1905.  
  1906.  
  1907.  
  1908. /* Return information for the currently active TextEdit control.  The currently
  1909. ** active TextEdit control is stored in gActiveTEHndl, and can be accessed
  1910. ** directly.  If gActiveTEHndl is nil, then there is no currently active one.
  1911. ** The information that we return is the viewRect and window of the active
  1912. ** TextEdit control.  This is information that could be gotten directly, but
  1913. ** this call makes it a little more convenient.
  1914. */
  1915.  
  1916. #pragma segment Controls
  1917. WindowPtr    CTETargetInfo(TEHandle *teHndl, Rect *teView)
  1918. {
  1919.     SetRect(teView, 0, 0, 0, 0);
  1920.     if (!(*teHndl = gActiveTEHndl)) return(nil);
  1921.  
  1922.     *teView = (*gActiveTEHndl)->viewRect;
  1923.     return((*gActiveTEHndl)->inPort);
  1924. }
  1925.  
  1926.  
  1927.  
  1928. /*****************************************************************************/
  1929.  
  1930.  
  1931.  
  1932. #pragma segment Controls
  1933. void    CTEUndo(void)
  1934. {
  1935.     TEHandle        teHndl;
  1936.     ControlHandle    viewCtl;
  1937.     CTEDataHndl        teData;
  1938.     Handle            hText, uText;
  1939.     short            oldStart, oldEnd;
  1940.  
  1941.     if (!(teHndl = gActiveTEHndl)) return;
  1942.  
  1943.     if (viewCtl = CTEViewFromTE(teHndl)) {
  1944.         teData = (CTEDataHndl)(*viewCtl)->contrlData;
  1945.         hText  = (*teHndl)->hText;
  1946.         uText  = (*teData)->undoText;
  1947.         if (!uText) return;        /* There is no undo yet.  Getting called in
  1948.                                 ** this state is actually an error, but we
  1949.                                 ** check, just in case.
  1950.                                 */
  1951.         oldStart = (*teHndl)->selStart;
  1952.         oldEnd   = (*teHndl)->selEnd;
  1953.         (*teData)->undoText = CTESwapText(teHndl, (*teData)->undoText, false);
  1954.         (*teHndl)->selStart = (*teHndl)->selEnd = 0;
  1955.         CTEUpdate(teHndl, viewCtl, false);
  1956.         CTESetSelect((*teData)->undoSelStart, (*teData)->undoSelEnd, teHndl);
  1957.             /* CTESetSelect flags it as a new undo situation for us. */
  1958.         (*teData)->undoSelStart = oldStart;
  1959.         (*teData)->undoSelEnd   = oldEnd;
  1960.     }
  1961. }
  1962.  
  1963.  
  1964.  
  1965. /*****************************************************************************/
  1966.  
  1967.  
  1968.  
  1969. /* Draw the TextEdit control and frame. */
  1970.  
  1971. #pragma segment Controls
  1972. void    CTEUpdate(TEHandle teHndl, ControlHandle ctl, Boolean justShowActive)
  1973. {
  1974.     WindowPtr    oldPort, tePort;
  1975.     Rect        viewRect, brdrRect;
  1976.     CTEDataHndl    teData;
  1977.     short        mode;
  1978.  
  1979.     if (teHndl) {
  1980.         if ((*ctl)->contrlVis) {
  1981.  
  1982.             GetPort(&oldPort);
  1983.             SetPort(tePort = (*teHndl)->inPort);
  1984.  
  1985.             teData = (CTEDataHndl)(*ctl)->contrlData;
  1986.             viewRect = (*teHndl)->viewRect;
  1987.             brdrRect = (*teData)->brdrRect;
  1988.             FrameRect(&brdrRect);
  1989.     
  1990.             if (!justShowActive) {
  1991.                 EraseRect(&viewRect);
  1992.                 TEUpdate(&viewRect, teHndl);
  1993.             }
  1994.     
  1995.             mode = (*teData)->mode;
  1996.             if (mode & cteShowActive) {
  1997.                 InsetRect(&brdrRect, -3, -3);
  1998.                 if (mode & cteVScroll) brdrRect.right  += 15;
  1999.                 if (mode & cteHScroll) brdrRect.bottom += 15;
  2000.                 if ((tePort != FrontWindow()) || (teHndl != gActiveTEHndl)) PenPat(qd.white);
  2001.                 PenSize(2, 2);
  2002.                 FrameRect(&brdrRect);
  2003.                 PenNormal();
  2004.             }
  2005.  
  2006.             SetPort(oldPort);
  2007.         }
  2008.     }
  2009. }
  2010.  
  2011.  
  2012.  
  2013. /*****************************************************************************/
  2014.  
  2015.  
  2016.  
  2017. /* Return the control handle for the view control that owns the TextEdit
  2018. ** record.  Use this to find the view to do customizations such as changing
  2019. ** the update procedure for this TextEdit control.
  2020. */
  2021.  
  2022. #pragma segment Controls
  2023. ControlHandle    CTEViewFromTE(TEHandle teHndl)
  2024. {
  2025.     WindowPtr        window;
  2026.     ControlHandle    viewCtl;
  2027.     TEHandle        te;
  2028.  
  2029.     if (!teHndl) return(nil);
  2030.  
  2031.     window = (WindowPtr)(*teHndl)->inPort;
  2032.  
  2033.     for (viewCtl = nil;;) {
  2034.  
  2035.         viewCtl = CTENext(window, &te, viewCtl);
  2036.         if ((!viewCtl) || (te == teHndl)) return(viewCtl);
  2037.     }
  2038. }
  2039.  
  2040.  
  2041.  
  2042. /*****************************************************************************/
  2043.  
  2044.  
  2045.  
  2046. /* Call this when a window with TextEdit controls is being activated.  This
  2047. ** will make the TextEdit control that was last active in this window the
  2048. ** active TextEdit control again.  If it can't find one that was the last
  2049. ** active control, it makes the first one it finds the active control.
  2050. */
  2051.  
  2052. #pragma segment Controls
  2053. Boolean    CTEWindActivate(WindowPtr window)
  2054. {
  2055.     short            hilite, scrollNum;
  2056.     Boolean            oneIsActive;
  2057.     ControlHandle    viewCtl, scrollCtl;
  2058.     TEHandle        te;
  2059.     CTEDataHndl        teData;
  2060.  
  2061.     if (!window) return(false);
  2062.  
  2063.     hilite = 255;
  2064.     if (window == FrontWindow()) hilite = 0;
  2065.  
  2066.     for (oneIsActive = false, viewCtl = nil;;) {
  2067.  
  2068.         viewCtl = CTENext(window, &te, viewCtl);
  2069.         if (!viewCtl) break;
  2070.             /* The only way out of the loop. */
  2071.  
  2072.         teData = (CTEDataHndl)(*viewCtl)->contrlData;
  2073.         if ((*teData)->mode & cteActive) {
  2074.             if (!hilite) {
  2075.                 CTEActivate(true, te);
  2076.                 oneIsActive = true;
  2077.             }
  2078.             else CTEActivate(false, te);
  2079.         }
  2080.  
  2081.         for (scrollNum = 0; scrollNum < 2; ++scrollNum) {
  2082.             scrollCtl = CTEScrollFromTE(te, scrollNum);
  2083.             if (scrollCtl)
  2084.                 HiliteControl(scrollCtl, hilite);
  2085.         }
  2086.     }
  2087.  
  2088.     return(oneIsActive);
  2089. }
  2090.  
  2091.  
  2092.  
  2093. /*****************************************************************************/
  2094. /*****************************************************************************/
  2095.  
  2096.  
  2097.  
  2098. #pragma segment Controls
  2099. pascal void    VActionProc(ControlHandle scrollCtl, short part)
  2100. {
  2101.     short        lineHeight, delta, value, teOffset;
  2102.     short        oldValue, max;
  2103.     TEHandle    te;
  2104.     
  2105.     if (part) {                        /* If it was actually in the control. */
  2106.  
  2107.         te = gActiveTEHndl;
  2108.         lineHeight = (*te)->lineHeight;
  2109.  
  2110.         switch (part) {
  2111.             case inUpButton:
  2112.             case inDownButton:        /* One line. */
  2113.                 delta = lineHeight;
  2114.                 break;
  2115.             case inPageUp:            /* One page. */
  2116.             case inPageDown:
  2117.                 delta = (*te)->viewRect.bottom - (*te)->viewRect.top;
  2118.                 if (delta > lineHeight) delta -= lineHeight;
  2119.                 break;
  2120.         }
  2121.         if ( (part == inUpButton) || (part == inPageUp) )
  2122.             delta = -delta;        /* Reverse direction for an upper. */
  2123.  
  2124.         value = (oldValue = GetCtlValue(scrollCtl)) + delta;
  2125.         if (value < 0) value = 0;
  2126.         if (value > (max = GetCtlMax(scrollCtl))) value = max;
  2127.  
  2128.         if (value != oldValue) {
  2129.             SetCtlValue(scrollCtl, value);
  2130.             teOffset = (*te)->viewRect.top - (*te)->destRect.top;
  2131.             if (value -= teOffset) TEScroll(0, -value, te);
  2132.         }
  2133.     }
  2134. }
  2135.  
  2136.  
  2137.  
  2138. /*****************************************************************************/
  2139.  
  2140.  
  2141.  
  2142. #pragma segment Controls
  2143. pascal void    HActionProc(ControlHandle scrollCtl, short part)
  2144. {
  2145.     short        lineHeight, delta, value, teOffset;
  2146.     short        oldValue, max;
  2147.     TEHandle    te;
  2148.     
  2149.     if (part) {                        /* If it was actually in the control. */
  2150.  
  2151.         te = gActiveTEHndl;
  2152.         lineHeight = (*te)->lineHeight;
  2153.  
  2154.         switch (part) {
  2155.             case inUpButton:
  2156.             case inDownButton:        /* One line. */
  2157.                 delta = lineHeight;
  2158.                 break;
  2159.             case inPageUp:            /* One page. */
  2160.             case inPageDown:
  2161.                 delta = (*te)->viewRect.right - (*te)->viewRect.left;
  2162.                 if (delta > lineHeight) delta -= lineHeight;
  2163.                 break;
  2164.         }
  2165.         if ( (part == inUpButton) || (part == inPageUp) )
  2166.             delta = -delta;        /* Reverse direction for an upper. */
  2167.  
  2168.         value = (oldValue = GetCtlValue(scrollCtl)) + delta;
  2169.         if (value < 0) value = 0;
  2170.         if (value > (max = GetCtlMax(scrollCtl))) value = max;
  2171.  
  2172.         if (value != oldValue) {
  2173.             SetCtlValue(scrollCtl, value);
  2174.             teOffset = (*te)->viewRect.left - (*te)->destRect.left;
  2175.             if (value -= teOffset) TEScroll(-value, 0, te);
  2176.         }
  2177.     }
  2178. }
  2179.  
  2180.  
  2181.  
  2182. /*****************************************************************************/
  2183.  
  2184.  
  2185.  
  2186. /* This function is called after an edit to make sure that there is no extra
  2187. ** white space at the bottom of the viewRect.  If there are blank lines at
  2188. ** the bottom of the viewRect, and there is text scrolled off the top of the
  2189. ** viewRect, then the TextEdit control is scrolled to fill this space, or as
  2190. ** much of it as possible.
  2191. */
  2192.  
  2193. #pragma segment Controls
  2194. void    AdjustTEBottom(TEHandle teHndl)
  2195. {
  2196.     Rect    destRect, viewRect;
  2197.     short    botDiff, topDiff;
  2198.  
  2199.     destRect = (*teHndl)->destRect;
  2200.     viewRect = (*teHndl)->viewRect;
  2201.     destRect.bottom = destRect.top + CTEDocHeight(teHndl);
  2202.  
  2203.     botDiff = viewRect.bottom - destRect.bottom;
  2204.     if (botDiff > 0) {
  2205.         topDiff = viewRect.top - destRect.top;
  2206.         if (botDiff > topDiff) botDiff = topDiff;
  2207.         if (botDiff) TEScroll(0, botDiff, teHndl);
  2208.     }
  2209.  
  2210. }
  2211.  
  2212.  
  2213.  
  2214. /*****************************************************************************/
  2215.  
  2216.  
  2217.  
  2218. /* Bring the scrollbar values up to date with the current document position
  2219. ** and length.
  2220. */
  2221.  
  2222. #pragma segment Controls
  2223. void    AdjustScrollValues(TEHandle teHndl)
  2224. {
  2225.     short            scrollNum;
  2226.     ControlHandle    scrollCtl;
  2227.  
  2228.     for (scrollNum = 0; scrollNum < 2; ++scrollNum) {
  2229.         scrollCtl = CTEScrollFromTE(teHndl, scrollNum);
  2230.         if (scrollCtl)
  2231.             AdjustOneScrollValue(teHndl, scrollCtl, scrollNum);
  2232.     }
  2233. }
  2234.  
  2235.  
  2236.  
  2237. /*****************************************************************************/
  2238.  
  2239.  
  2240.  
  2241. /* Bring one scrollbar value up to date with the current document position
  2242. ** and length.
  2243. */
  2244.  
  2245. #pragma segment Controls
  2246. void    AdjustOneScrollValue(TEHandle teHndl, ControlHandle ctl, Boolean vert)
  2247. {
  2248.     Boolean    front;
  2249.     short    textPix, viewPix;
  2250.     short    max, oldMax, value, oldValue;
  2251.  
  2252.     front = ((*ctl)->contrlOwner == FrontWindow());
  2253.  
  2254.     oldValue = GetCtlValue(ctl);
  2255.     oldMax   = GetCtlMax(ctl);
  2256.  
  2257.     if (vert) {
  2258.         textPix = CTEDocHeight(teHndl);
  2259.         viewPix = (*teHndl)->viewRect.bottom - (*teHndl)->viewRect.top;
  2260.     }
  2261.     else {
  2262.         textPix = (*teHndl)->destRect.right - (*teHndl)->destRect.left;
  2263.         viewPix = (*teHndl)->viewRect.right - (*teHndl)->viewRect.left;
  2264.     }
  2265.     max = textPix - viewPix;
  2266.  
  2267.     if (max < 0) max = 0;
  2268.     if (max != oldMax) {
  2269.         if (front) SetCtlMax(ctl, max);
  2270.         else       (*ctl)->contrlMax = max;
  2271.     }
  2272.  
  2273.     if (vert)
  2274.         value = (*teHndl)->viewRect.top  - (*teHndl)->destRect.top;
  2275.     else
  2276.         value = (*teHndl)->viewRect.left - (*teHndl)->destRect.left;
  2277.  
  2278.     if (value < 0)   value = 0;
  2279.     if (value > max) value = max;
  2280.     if (value != oldValue) {
  2281.         if (front) SetCtlValue(ctl, value);
  2282.         else       (*ctl)->contrlValue = value;
  2283.     }
  2284. }
  2285.  
  2286.  
  2287.  
  2288. /*****************************************************************************/
  2289.  
  2290.  
  2291.  
  2292. #pragma segment Controls
  2293. Boolean    IsScrollBar(ControlHandle ctl)
  2294. {
  2295.     Rect            dummy;
  2296.     ControlHandle    dummyCtl;
  2297.  
  2298.     if (!ctl) return(false);
  2299.  
  2300.     if (!scrollProc) {
  2301.         SetRect(&dummy, 0, 0, 0, 0);
  2302.         dummyCtl = NewControl((*ctl)->contrlOwner, &dummy, "", 1, false, 0, 1, scrollBarProc, 0L);
  2303.         if (dummyCtl) {
  2304.             scrollProc = (*dummyCtl)->contrlDefProc;
  2305.             DisposeControl(dummyCtl);
  2306.         }
  2307.     }
  2308.  
  2309.     return((*ctl)->contrlDefProc == scrollProc);
  2310. }
  2311.  
  2312.  
  2313.  
  2314.